#include "implicitly.hpp"

#define pi 3.1415926535

/*
 ;
 ;   Constructor - make sure to specify correct size and offset
 ;
 ;	 Use ::Attach once the target window is created
 ;
*/
imGFX::imGFX(int width, int height, int xoffset, int yoffset) {
	dib_width = width;
	dib_height = height;
	dib_x_off = xoffset;
	dib_y_off = yoffset;

	m_hWnd = NULL;
	m_dc = NULL;
	p_dib = NULL;
	bAttached = false;

	// rainbow stuff
	increasing = true;
	gradient = new char[dib_width * dib_height * 4];
	iColorIndex = NULL;

	phase1 = 1;
	phase2 = 3;
	phase3 = 5;

	//sine stuff
	x_offset = dib_width;
	x_rate = 30;
	y_rate = 15;

	fadecounter = 0;

	sine_table = new int[dib_width];

	table_generated = false;
	
	bNfo = false;

	nfo_y_off = 0;
}

imGFX::imGFX() {
	m_hWnd = NULL;
	m_dc = NULL;
	p_dib = NULL;
	bAttached = false;

	// rainbow stuff
	increasing = true;
	gradient = new char[dib_width * dib_height * 4];
	iColorIndex = NULL;

	phase1 = 1;
	phase2 = 3;
	phase3 = 5;

	//sine stuff
	x_offset = dib_width;
	x_rate = 30;
	y_rate = 15;

	fadecounter = 0;

	sine_table = new int[dib_width];

	table_generated = false;

	bNfo = false;
	
	nfo_y_off = 0;
}

imGFX::~imGFX() {
	ReleaseDC(m_hWnd, m_dc);
	delete[] gradient;
	delete[] sine_table;
}

/*
 ;
 ;	Attaches to the target window and creates the DIB section - alternatively, use CreateCompatibleBitmap
 ;
*/
bool imGFX::Attach(HWND hWnd) {
	if(hWnd == NULL)
		return false;
	m_hWnd = hWnd;
	
	BITMAPINFO* DiBPlane = new BITMAPINFO;
	ZeroMemory(DiBPlane, sizeof(BITMAPINFO));
	DiBPlane->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	DiBPlane->bmiHeader.biWidth = dib_width;
	DiBPlane->bmiHeader.biHeight = dib_height;
	DiBPlane->bmiHeader.biPlanes = 1;
	DiBPlane->bmiHeader.biBitCount = 32;
	
	m_dc = CreateCompatibleDC(GetDC(m_hWnd));
	hBMAP = CreateDIBSection(m_dc, DiBPlane, NULL, (void**)&p_dib, NULL, NULL);
	SelectObject(m_dc, hBMAP);
	SetBkMode(m_dc, TRANSPARENT);
	delete[] DiBPlane;
	return true;
}

/*
 ; Detaches - does not delete any other objects or null anything besides window variables
 ; 
 ; Can be attached to another HWND to have the same effect
*/
void imGFX::Detach() {
	ReleaseDC(m_hWnd, m_dc);
	m_hWnd = NULL;
	RestoreDC(hFaceDC,SavedDC);
	DeleteObject(bFace);
}

void imGFX::ShapeWindow(int IDBBitmap) {
	HDC hDC;
	BITMAP bmap;
	HRGN combine;
	
	hDC = GetDC(m_hWnd);
	hFaceDC = CreateCompatibleDC(hDC);
	bFace = LoadBitmap((HINSTANCE)GetWindowLong(m_hWnd, -6), MAKEINTRESOURCE(IDBBitmap));
	GetObject(bFace, sizeof(BITMAP), &bmap);
	SavedDC = SaveDC(hFaceDC);
	SelectObject(hFaceDC, bFace);
	MoveWindow(m_hWnd, 0, 0, bmap.bmWidth, bmap.bmHeight, true);
	SendMessage(m_hWnd, WM_PAINT, 0, 0);

	HRGN temp;
	COLORREF trans, test;
	// upper pixel is transparent
	trans = GetPixel(hFaceDC, 0, 0);
	combine = CreateRectRgn(0, 0, 0, 0);
	temp = CreateRectRgn(0, 0, 0, 0);

	for(int y = 0; y < bmap.bmHeight; ++y)
		for(int x = 0; x < bmap.bmWidth; ++x) {
			test = GetPixel(hFaceDC, x, y);
			if(test != trans) {
				int startx = x;
				while( x < bmap.bmWidth && (GetPixel(hFaceDC, x, y) != trans)) {
					++x;
				}
				--x;
				temp = CreateRectRgn(startx, y, x, y+1);
				CombineRgn(combine, combine, temp, RGN_OR);
			}
		}

	SetWindowRgn(m_hWnd, combine, true);
	ShowWindow(m_hWnd, SW_SHOW);
	UpdateWindow(m_hWnd);
	ReleaseDC(m_hWnd, hDC);
	ReleaseDC(m_hWnd, hFaceDC);
	DeleteObject(combine);
	DeleteObject(temp);
}

void imGFX::DumpBitmap(int IDBBitmap, char* FileName) {
	HDC hDC, 
		hFaceDC;
	HBITMAP hFaceB;
	BITMAP bmap;
	int SavedDC;
	
	hDC = GetDC(m_hWnd);
	hFaceDC = CreateCompatibleDC(hDC);
	hFaceB = LoadBitmap((HINSTANCE)GetWindowLong(m_hWnd, -6), MAKEINTRESOURCE(IDBBitmap));
	GetObject(hFaceB, sizeof(BITMAP), &bmap);
	SelectObject(hFaceDC, hFaceB);
	SavedDC = SaveDC(hFaceDC);
	MoveWindow(m_hWnd, 0, 0, bmap.bmWidth, bmap.bmHeight, true);
	SendMessage(m_hWnd, WM_PAINT, 0, 0);

	DWORD* dwPixels = new DWORD[bmap.bmWidth * bmap.bmHeight];

	for(int y = 0; y < bmap.bmHeight; ++y)
		for(int x = 0; x < bmap.bmWidth; ++x) {
			dwPixels[y * bmap.bmWidth + x] = GetPixel(hFaceDC, x, y);
		}

	char buffer[124];

	FILE* fout = fopen(FileName, "wb+");
	fwrite("unsigned char bmpDump = { ", 27, 1, fout);
	for(int i = 0; i < bmap.bmWidth * bmap.bmHeight; ++i) {
		fwrite(dwPixels, bmap.bmHeight * bmap.bmWidth * 4, 1, fout);
	}
	fclose(fout);

	delete[] dwPixels;
	ReleaseDC(m_hWnd, hDC);
	RestoreDC(hFaceDC,SavedDC);
	ReleaseDC(m_hWnd, hFaceDC);
	DeleteObject(hFaceB);

}

void imGFX::LoadResBitmap(int IDBBitmap, unsigned char* lpBits) {
	HBITMAP bFace = LoadBitmap((HINSTANCE)GetWindowLong(m_hWnd, -6), MAKEINTRESOURCE(IDBBitmap));
	HDC hDC = GetDC(m_hWnd);
	HDC hFaceDC = CreateCompatibleDC(hDC);
	SelectObject(hFaceDC, bFace);
	BITMAPINFO bmi = {0};
	bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
	bmi.bmiHeader.biBitCount = 32;
	HDC hTemp = CreateCompatibleDC(NULL);
	GetDIBits(hTemp, bFace, 0, 200, &lpBits, &bmi, DIB_RGB_COLORS);
	int z = GetLastError();
	ReleaseDC(NULL, hTemp);
	ReleaseDC(m_hWnd, hDC);
	ReleaseDC(m_hWnd, hFaceDC);
	DeleteObject(bFace);
}

void imGFX::DrawBitmap(unsigned char* lpBits, int x, int y, int sizex, int sizey) {
//	HDC hDC = GetDC(m_hWnd);
//	HDC hFaceDC = CreateCompatibleDC(hDC);
//	SelectObject(hFaceDC, bFace);
	for(int i = sizey, n = 0; i > 0, n < sizey; --i, ++n) 
		for(int z = 0; z < sizex; ++z) {
			static int location = 0;
			location = n * sizex + z;
			if(lpBits[location * 4] != 0xFE) {
				p_dib[((y + i) * dib_width + x + z) * 4] = lpBits[location * 4];
				p_dib[((y + i) * dib_width + x + z) * 4 + 1] = lpBits[location * 4 + 1];
				p_dib[((y + i) * dib_width + x + z) * 4 + 2] = lpBits[location * 4 + 2];
			}
		}
//	ReleaseDC(m_hWnd, hDC);
//	ReleaseDC(m_hWnd, hFaceDC);
}

void imGFX::DrawButton(sbutton button, TCHAR* Text, unsigned int r, unsigned int g, unsigned int b) {
	unsigned char *palette = new unsigned char[button.sizeY * 4];

	if(button.bDown) {
		r -= 0x25;
		g -= 0x25;
		b -= 0x25;
	} else if(button.bOver) {
		r -= 0x03;
		g -= 0x03;
		b -= 0x03;
	}
	
	Wrap(r, 0, 255);
	Wrap(g, 0, 255);
	Wrap(b, 0, 255);

	CreateGradient(palette, RGB(r, g, b), 0x00202020, button.sizeY);

	int tempY = button.y;
	tempY += 256;
	for(int y = button.sizeY / 2; y < button.sizeY; ++y) {
		static int location = 0;
		location = y * 4;
		for(int x = 0; x < button.sizeX; ++x) {
			AddPixel(x + button.x, y + tempY, palette[location + 2], palette[location + 1], palette[location]);
		}
	}

	for(int y = button.sizeY / 2; y > 0; --y) {
		static int location = 0;
		location = (button.sizeY - y) * 4;
		for(int x = 0; x < button.sizeX; ++x) {
			AddPixel(x + button.x, y + tempY, palette[location + 2], palette[location + 1], palette[location]);
		}
	}

	SIZE charSize;
	GetTextExtentPoint32(m_dc, Text, _tcslen(Text), &charSize);
	PrintText(button.x + (button.sizeX - charSize.cx) / 2, 256 - button.y - button.sizeY + (button.sizeY - charSize.cy) / 2, Text, 0xdf, 0xdf, 0xdf);

	delete[] palette;
}

bool imGFX::IsOver(sbutton area, LPARAM lParam) {
	LONG MouseX = (LOWORD(lParam));
	LONG MouseY = (HIWORD(lParam));
	MouseY = 256 - MouseY;

	int left = area.x;
	int right = area.x + area.sizeX;
	int top = area.y;
	int bottom = (area.y - area.sizeY);
	if(MouseX >= left && MouseX <= right)
		if(MouseY >= bottom + area.sizeY && MouseY  <= top + area.sizeY)
			return true;
	return false;
}

/*
    Function:	AddPixel()
       Input:	Position, color, buffer for dib
 Description:	Essentially SetPixel() but for dibs and less resource heavy
*/
void imGFX::AddPixel(int x, int y, unsigned char r, unsigned char g, unsigned char b) {
	if(x + 1 && y + 1 && x < dib_width && y < dib_height) {
        static int location = 0;
		location = y * dib_width + x;
        p_dib[location * 4] = b;
        p_dib[location * 4 + 1] = g;
        p_dib[location * 4 + 2] = r;
	}
}

void imGFX::AddTranPixel(int x, int y, unsigned char r, unsigned char g, unsigned char b, unsigned char a) {
	if(x + 1 && y + 1 && x < dib_width && y < dib_height) {
        static int location = 0;
		location = y * dib_width + x;
        p_dib[location * 4] = (b * a + (1 - a) * p_dib[location * 4]);
        p_dib[location * 4 + 1] = (g * a + (1 - a) * p_dib[location * 4 + 1]);
        p_dib[location * 4 + 2] = (r * a + (1 - a) * p_dib[location * 4 + 2]);
	}
}

DWORD imGFX::GetDIBPixel(int x, int y) {
	if(x + 1 && y + 1 && x < dib_width && y < dib_height) {
		static int location = 0;
		location = y * dib_width + x;
		DWORD retVal = 0;
		memcpy(&retVal, &p_dib[location], sizeof(DWORD));
		return retVal;
	}
	return -1;
}

/*
 ; Draws a filled rectangle
*/
void imGFX::DrawRect(int x, int y, int width, int height, unsigned char r, unsigned char g, unsigned char b) 
{
	 for(int z = 0; z < width; ++z) {
		for(int i = 0; i < height; ++i) {
			AddPixel(x + z, y + i, r, g, b);
		}
	}
}

/*
 ; Draws a 2x2 dot
*/
void imGFX::DrawDot(int x, int y) {
	for(int z = 0; z < 2; ++z) {
		for(int i = 0; i < 2; ++i) {
			AddPixel(x + z, y + i, 0xff, 0xff, 0xfe);
		}
	}
}

/*
 ; SetTextColor wrapper
*/
void imGFX::TextColor(unsigned char r, unsigned char g, unsigned char b) {
	SetTextColor(m_dc, RGB(r, g, b));
}

/*
 ; Draws text out in color, if alpha (0xFE) will be colored with gradient
*/
void imGFX::VanillaText(int x, int y, TCHAR* Text) {
	HFONT tFont = CreateFont(6, 6, 0, 0, 500, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, TEXT("Terminal"));
	SelectObject(m_dc, tFont);
	TextColor(0x00, 0x00, 0xfe);

	RECT rect;
	rect.left = x;
	rect.top = y;
	DrawText(m_dc, Text, _tcslen(Text), &rect, DT_NOCLIP);
	DeleteObject(tFont);
}

void imGFX::PrintText(int x, int y, TCHAR* Text, unsigned char r, unsigned char g, unsigned char b) {
	HFONT tFont = CreateFont(6, 6, 0, 0, 500, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, TEXT("Terminal"));
	SelectObject(m_dc, tFont);
	TextColor(r, g, b);

	RECT rect;
	rect.left = x;
	rect.top = y;
	DrawText(m_dc, Text, _tcslen(Text), &rect, DT_NOCLIP);
	DeleteObject(tFont);
}

void imGFX::ScrollText(int y, TCHAR* Text) {
	HFONT tFont = CreateFont(6, 6, 0, 0, 900, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, TEXT("Terminal"));
	SelectObject(m_dc, tFont);
	TextColor(0x00, 0x00, 0xfe);

	SIZE charSize;

	GetTextExtentPoint32(m_dc, Text, _tcslen(Text), &charSize);

	static int l_x = dib_width + charSize.cx;
	int pos = 0;
	
	if(l_x < 0) 
		l_x = dib_width + charSize.cx;

	if(!table_generated)
		GenerateTable();

	x_offset -= 2;
	--l_x; 
	pos = x_offset;
	for(int i = 0; i < _tcslen(Text); ++i) {
		GetTextExtentPoint32(m_dc, &Text[i], 1, &charSize);
		pos += charSize.cx;
		TextOut(m_dc, pos, sine_table[(pos + l_x) % 360 /* 360 degree rotation, change for teardrop */] + y, &Text[i], 1);
	}

	if(pos < 0) 
		x_offset = dib_width;
	DeleteObject(tFont);
}

void imGFX::WaveBanner(int x, int y, TCHAR* Text) {
	HFONT tFont = CreateFont(6, 6, 0, 0, 900, 0, 0, 0, DEFAULT_CHARSET, 0, 0, 0, 0, TEXT("Terminal"));
	SelectObject(m_dc, tFont);
	TextColor(0x00, 0x00, 0xfe);
	SIZE charSize;

	GetTextExtentPoint32(m_dc, Text, _tcslen(Text), &charSize);

	static int l_z = dib_width + charSize.cx;
	static int pos = x;
	static int new_x_off = x_offset;
	
	if(l_z < 0) 
		l_z = dib_width + charSize.cx;

	if(!table_generated)
		GenerateTable();

	--l_z; 
	new_x_off -= 4;

	pos = x;
	GetTextExtentPoint32(m_dc, &Text[0], 1, &charSize);
	for(int i = 0; i < _tcslen(Text); ++i) {
		if(Text[i] == '\n') {
			y += charSize.cy;
			pos = x;
		} else {
				pos += charSize.cx;
			TextOut(m_dc, pos, sine_table[(pos + l_z) % 360 /* 360 degree rotation, change for teardrop */] + y, &Text[i], 1);
		}
	}
	DeleteObject(tFont);
}

void imGFX::GenerateTable() {
	double full = 2 * pi / 180;

	for(int i = 0; i < dib_width; i++) {
		if(i == x_rate)
			--x_rate; // avoid wierd skips
		sine_table[i] =  sin((float) i /  x_rate) * y_rate;
	}

	++table_generated;
}

/*
void imGFX::AddText(unsigned char* Font, TCHAR* oText, int x, int y, int fontx, int fonty) {
		// get the font indexes and draw letters
	for(int len = 0; len < _tcslen(oText); ++len) {
		int LetterIndex = oText[len] - ' ';
		LetterIndex *= fontx;
		for(int i = 0; i < fonty; ++i) 
			for(int z = 0; z < fontx; ++z) {
				p_dib[((y + i) * dib_width + x + fontx * len + z) * 4] = Font[((fonty - i) * fontx + LetterIndex + z) * 3];
				p_dib[((y + i) * dib_width + x + fontx * len + z) * 4 + 1] = Font[((fonty - i) * fontx + LetterIndex + z) * 3 + 1];
				p_dib[((y + i) * dib_width + x + fontx * len + z) * 4 + 2] = Font[((fonty - i) * fontx + LetterIndex + z) * 3 + 2];
		}
	}
}
*/

/*
 ; Creates a rainbow gradient and fills a char array the size of the screen because I'm too lazy to do it individually calculating
 ;
 ; sine(frequency * i + phase) * amplitude + center
 ;
 ; ;)
*/
void imGFX::CreateRainbows() {
	// generate a color chart that is the screen width
	double	frequency1 = 0.007,
			frequency2 = 0.007,
			frequency3 = 0.007;

	for(int y = 0; y < dib_height; ++y) {
		for(int x = y + iColorIndex, i = dib_width; i >= 0; ++x, --i) {
			unsigned char red = sin(frequency1 * x + phase1) * 127 + 128;
			unsigned char green = sin(frequency2 * x + phase2) * 127 + 128;
			unsigned char blue = sin(frequency3 * x + phase3) * 127 + 128;
			gradient[(y * dib_width + i) * 4 + 2] = red;
			gradient[(y * dib_width + i) * 4 + 1] = green;
			gradient[(y * dib_width + i) * 4] = blue;
		}
	}
	
	DWORD dwColor = NULL; // stores old color, must be 4 bytes
	for(int y = 0; y < dib_height; ++y) {
		for(int x = 0; x < dib_width; ++x) {
			dwColor = p_dib[(y * dib_width + x) * 4];
			if(dwColor == 0xFE) {
				unsigned char r = gradient[(y * dib_width + x) * 4 + 2];
				unsigned char g =	gradient[(y * dib_width + x) * 4 + 1];
				unsigned char b = gradient[(y * dib_width + x) * 4];
				AddPixel(x, y, r, g, b);
			}
		}
	}
	iColorIndex += 6;
	iColorIndex & dib_width;
	
	if(phase3 > 6.5 || phase3 < 3.5)
		increasing = !increasing;
	if(increasing) {
		phase3 += 0.01;
	}
	if(!increasing) {
		phase3 -= 0.01;
	}
}

void imGFX::CreateGradient(unsigned char* outpalette, DWORD fromColor, DWORD toColor, int steps) {
	int i;
	float n;
	unsigned char r, g, b;

	for( i = 0; i < steps; i++ )
	{
		n = (float)i / (float) (steps-1);
		r = (float)GetRValue(fromColor) * (1.0f-n) + (float)GetRValue(toColor) * n;
		g = (float)GetGValue(fromColor) * (1.0f-n) + (float)GetGValue(toColor) * n;
		b = (float)GetBValue(fromColor) * (1.0f-n) + (float)GetBValue(toColor) * n;

		r & 255;
		g & 255;
		b & 255;
		outpalette[i * 4] = b;
		outpalette[i * 4 + 1] = g;
		outpalette[i * 4 + 2] = r;
	}
}

void imGFX::ShowNfo() {
	bNfo = !bNfo;

	RECT rWindow = {0};
	int iHeight = dib_height - 256;

	if(bNfo)
		 iHeight += 256;
	GetWindowRect(m_hWnd, &rWindow);
	SetWindowPos(m_hWnd, 
				 HWND_TOP,
				 rWindow.left,
				 rWindow.top,
				 rWindow.right - rWindow.left,
				 iHeight,
				 SWP_NOREPOSITION);
}

void imGFX::DrawNfo(TCHAR* nfo) {
	SIZE cSize;
	GetTextExtentPoint32(m_dc, nfo, 1, &cSize);
	rNfo.top = dib_height - 256 - nfo_y_off + cSize.cy * 1.5 + 16;
	rNfo.bottom = dib_height - cSize.cy * 1.5;
	rNfo.left = 4;
	rNfo.right = cSize.cx * 80 + rNfo.left;
	TextColor(0x00, 0x00, 0xfe);
	DrawText(m_dc, nfo, _tcslen(nfo), &rNfo, DT_NOCLIP);

	unsigned char r = GetRValue(background_color) + 030,
			     g = GetGValue(background_color) + 0x30,
				 b = GetBValue(background_color) + 0x30;

	// bottom border
	unsigned char *palette = new unsigned char[20 * 4];
	CreateGradient(palette, RGB(r, g, b), background_color, 20);

	for(int y = 0; y < 20; ++y) {
		int location = y * 4;
		for(int x = 0; x < dib_width; ++x) {
			AddPixel(x, y, palette[location + 2], palette[location + 1], palette[location]);
		}
	}
	delete[] palette;

	// top border
	palette = new unsigned char[10 * 4];
	CreateGradient(palette, RGB(r, g, b), background_color, 10);

	for(int y = 9; y > 0; --y) {
		int location = (10 - y) * 4;
		for(int x = 0; x < dib_width; ++x) {
			AddPixel(x, 256 - y, palette[location + 2], palette[location + 1], palette[location]);
		}
	}
	for(int y = 9; y > 0; --y) {
		int location = y * 4;
		for(int x = 0; x < dib_width; ++x) {
			AddPixel(x, 256 - y - 9, palette[location + 2], palette[location + 1], palette[location]);
		}
	}

	delete[] palette;
	
	TCHAR* szScroll = _T("Press the up/down arrowkeys to scroll.");

	TextColor(0xdf, 0xdf, 0xdf);

	RECT rAbout;
	rAbout.top = dib_height - cSize.cy * 1.5;
	rAbout.bottom = 0;
	GetTextExtentPoint32(m_dc, szScroll, _tcslen(szScroll), &cSize);
	rAbout.left = (dib_width - cSize.cx) / 2;
	rAbout.right = rAbout.left + cSize.cx;

	DrawText(m_dc, szScroll, _tcslen(szScroll), &rAbout, DT_NOCLIP);

	TextColor(0x00, 0x00, 0xfe);
}

/*
 ; Updates the buffer with double buffering
*/
bool imGFX::Update() {
	GdiFlush();
	HDC hDC = GetDC(m_hWnd);
	bool bBlit1 = BitBlt(m_dc, dib_x_off, dib_y_off, dib_width, dib_height, m_dc, 0, 0, SRCCOPY);
	bool bBlit2 = BitBlt(hDC, dib_x_off, dib_y_off, dib_width, dib_height, m_dc, 0, 0, SRCCOPY);
	ReleaseDC(m_hWnd, hDC);
	if(bBlit1 && bBlit2)
		return true;
	else return false;
}

/*
 ; Clears both onscreen buffer and dib buffer
*/
void imGFX::Clear() {
	memset((void*)p_dib, background_color, dib_width * dib_height * 4);
}

void imGFX::Primer() {
	memset((void*)(p_dib + dib_width * (dib_height - 256) * 4), background_color, dib_width * (dib_height - 256) * 4);
}

/*
 ; Wraps an integer around a specified range, instead of using and
*/
int imGFX::Wrap(int kX, const int kLowerBound, const int kUpperBound)
{
    int range_size = kUpperBound - kLowerBound + 1;

    if (kX < kLowerBound)
        kX += range_size * ((kLowerBound - kX) / range_size + 1);

    return kLowerBound + (kX - kLowerBound) % range_size;
}